package com.hn.javamail.service.impl;

import com.hn.javamail.constants.CommonConstant;
import com.hn.javamail.constants.MessageConstant;
import com.hn.javamail.exception.BusinessException;
import com.hn.javamail.result.Result;
import com.hn.javamail.service.MailService;
import com.hn.javamail.utils.RedisCache;
import com.hn.javamail.utils.ValidateUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.collections4.MapUtils;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.core.env.Environment;
import org.springframework.stereotype.Service;

import javax.annotation.Resource;
import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;

/**
 * @author Ts-SuSu
 * @date 2024/4/28 9:46
 * @description 邮件业务实现类
 */
@Service
@Slf4j
public class MailServiceImpl implements MailService {

    /**
     * 发件人邮箱
     */
    @Value("${mail.from_mail}")
    private String from_mail;

    /**
     * 发件人邮箱授权密码
     */
    @Value("${mail.from_password}")
    private String from_password;

    /**
     * 邮件类型
     */
    @Value("${mail.type}")
    private String type;

    /**
     * 环境
     */
    @Resource
    private Environment environment;

    /**
     * Redis缓存
     */
    @Resource
    private RedisCache redisCache;

    /**
     * 发送邮件
     * <p>
     * 实现步骤：
     * 1. 解析参数，获取收件人邮箱集合、标题和内容
     * 2. 对收件人邮箱集合进行校验，确保格式正确
     * 3. 获取邮件配置属性
     * 4. 创建邮件会话 Session
     * 5. 创建 MimeMessage 对象
     * 6. 设置发件人、收件人、标题和内容
     * 7. 发送邮件
     * </p>
     *
     * @param params 包含邮件相关参数的 Map
     * @return 发送结果
     * @throws MessagingException 如果发送邮件过程中发生错误
     */
    @Override
    public Result sendMail(Map<String, Object> params) throws MessagingException {
        // 1. 解析参数，获取收件人邮箱集合、标题和内容
        String mailRecipients = MapUtils.getString(params, "mailRecipients");
        if (!ValidateUtils.isRequired(mailRecipients)) {
            return Result.failed("收件人邮箱不能为空");
        }

        List<String> mailRecipientsList = Arrays.stream(mailRecipients.split(",")).map(String::trim).collect(Collectors.toList());
        String title = MapUtils.getString(params, "title");
        String content = MapUtils.getString(params, "content");

        try {
            // 2. 对收件人邮箱集合进行校验，确保格式正确
            mailRecipientsList.forEach(email -> {
                if (!ValidateUtils.isEmail(email)) {
                    throw new BusinessException(MessageConstant.EMAIL_IS_ERROR);
                }
            });

            // 3. 获取邮件配置属性
            Properties props = getMailPropertiesByEnvironmentAndMailType();

            // 4. 创建邮件会话 Session
            Session session = Session.getDefaultInstance(props, new Authenticator() {
                //验证发件人的身份信息
                protected PasswordAuthentication getPasswordAuthentication() {
                    return new PasswordAuthentication(from_mail, from_password);
                }
            });
            //session.setDebug(true);
            // 5. 创建 MimeMessage 对象
            MimeMessage message = new MimeMessage(session);
            message.setFrom(new InternetAddress(from_mail));

            // 6. 设置收件人、标题和内容
            Address[] recipients = new Address[mailRecipientsList.size()];
            for (int i = 0; i < mailRecipientsList.size(); i++) {
                recipients[i] = new InternetAddress(mailRecipientsList.get(i));
            }
            message.setRecipients(MimeMessage.RecipientType.TO, recipients);
            message.setSubject(title);
            message.setText(content);

            // 7. 发送邮件
            Transport.send(message);
        } catch (AuthenticationFailedException e) {
            // 捕获认证失败异常
            throw new BusinessException("邮件发送失败：邮箱认证失败，请检查授权码是否正确");
        } catch (MessagingException e) {
            // 其他邮件发送相关异常处理
            throw new BusinessException("邮件发送失败：" + e.getMessage());
        }
        return Result.succeed("发送成功");
    }


    /**
     * 根据当前环境和邮件类型获取邮件配置属性
     * <p>
     * 实现步骤：
     * 1. 创建邮件配置属性对象
     * 2. 设置默认的认证、STARTTLS和SSL信任主机配置
     * 3. 获取当前活动的profile（环境）
     * 4. 根据环境设置不同的邮件服务器配置和端口
     * 5. 根据邮件类型补充邮件服务器
     * 【注】：出于安全考虑，阿里云轻量应用服务器的【25】端口不能对外发送邮件，如果您有邮件发送需求，请使用【465】端口。
     * 所以正式环境请设置为 prod
     * </p>
     *
     * @return 邮件配置属性
     * @throws BusinessException 如果环境配置不正确或邮件类型不正确
     */
    private Properties getMailPropertiesByEnvironmentAndMailType() {
        // 1. 创建邮件配置属性对象
        Properties properties = new Properties();

        // 2. 设置默认的认证、STARTTLS和SSL信任主机配置
        properties.put("mail.smtp.auth", CommonConstant.SMTP_AUTH);
        //properties.put("mail.smtp.starttls.enable", CommonConstant.SMTP_STARTTLS_ENABLE);
        properties.setProperty("mail.transport.protocol", CommonConstant.SMTP);   // 使用的协议（JavaMail规范要求）

        // 3. 获取当前活动的profile（环境）
        String activeProfile = environment.getActiveProfiles()[0];
        log.info("当前环境标识：{}", activeProfile);

        // 4. 根据环境设置不同的邮件服务器配置和端口
        if (CommonConstant.DEV_ACTIVE.equals(activeProfile)) {
            // 判断邮件类型 如果是QQ邮箱,则使用SSL加密,QQ没有25端口,需要使用465端口
            if (CommonConstant.QQ_MAIL_TYPE.equals(type)) {
                properties.put("mail.smtp.port", CommonConstant.SSL_SMTP_PORT); // 使用465端口
                properties.put("mail.smtp.ssl.enable", CommonConstant.SMTP_SSL_ENABLE); // 启用SSL连接
                // 设置SSL连接所使用的Socket工厂类
                properties.setProperty("mail.smtp.socketFactory.class", CommonConstant.SSL_SOCKET_FACTORY_CLASS);
                // 设置是否在SSL连接失败时回退到普通Socket连接，设置为false表示不回退
                properties.setProperty("mail.smtp.socketFactory.fallback", CommonConstant.SSL_SOCKET_FACTORY_FALLBACK);
                // 设置SSL连接所使用的端口号，与mail.smtp.port一致
                properties.setProperty("mail.smtp.socketFactory.port", CommonConstant.SSL_SMTP_PORT);
            } else {
                properties.put("mail.smtp.port", CommonConstant.SMTP_PORT); // 使用25端口
            }
        } else if (CommonConstant.PROD_ACTIVE.equals(activeProfile)) {
            properties.put("mail.smtp.port", CommonConstant.SSL_SMTP_PORT); // 使用465端口
            properties.put("mail.smtp.ssl.enable", CommonConstant.SMTP_SSL_ENABLE); // 启用SSL连接
            properties.setProperty("mail.smtp.socketFactory.class", CommonConstant.SSL_SOCKET_FACTORY_CLASS);
            properties.setProperty("mail.smtp.socketFactory.fallback", CommonConstant.SSL_SOCKET_FACTORY_FALLBACK);
            properties.setProperty("mail.smtp.socketFactory.port", CommonConstant.SSL_SMTP_PORT);
        } else {
            // 抛出异常，表示未知的环境配置
            throw new BusinessException("未知的profile：" + activeProfile);
        }

        // 5. 根据邮件类型补充邮件服务器 【QQ/WY】
        log.info("当前邮件类型：{}", type);
        if (CommonConstant.QQ_MAIL_TYPE.equals(type)) {
            // 使用QQ邮箱配置
            properties.put("mail.smtp.host", CommonConstant.QQ_SMTP_HOST);
        } else if (CommonConstant.WY_MAIL_TYPE.equals(type)) {
            // 使用网易邮箱配置
            properties.put("mail.smtp.host", CommonConstant.WY_SMTP_HOST);
        } else {
            throw new BusinessException("未知的邮件类型：" + type);
        }
        return properties;
    }


    @Override
    public Result sendCode(Map<String, Object> params) throws MessagingException {
        //获取使用场景
        Integer usageScenario = MapUtils.getInteger(params, "usageScenario");
        if (!ValidateUtils.isRequired(usageScenario)) {
            throw new BusinessException("使用场景不能为空");
        }

        //获取收件人邮箱
        String mailRecipients = MapUtils.getString(params, "mailRecipients");
        if (!ValidateUtils.isRequired(mailRecipients)) {
            return Result.failed("收件人邮箱不能为空");
        }

        //创建随机对象
        Random random = new Random();
        switch (usageScenario) {
            case 1:
                Integer code = random.nextInt(9000) + 1000; // 生成1000到9999之间的随机整数
                String identifying = UUID.randomUUID().toString();
                //5分钟有效期
                redisCache.setCacheObject("verifyLogin:" + identifying, code, 5, TimeUnit.MINUTES);
                params.put("title", "验证码通知【登录】");
                params.put("content", "有一个人问我，什么是最重要的数字？我告诉他是您的验证码！您的验证码为：" + code + "。验证码有效期5分钟，请妥善保管好您的验证码!");
                return Result.succeed(sendMail(params), identifying);
            case 2:
                Integer code1 = random.nextInt(900000) + 100000; // 生成100000到999999之间的随机整数
                String identifying1 = UUID.randomUUID().toString();
                //5分钟有效期
                redisCache.setCacheObject("verifyPassGet:" + identifying1, code1, 3, TimeUnit.MINUTES);
                params.put("title", "验证码通知【密码找回】");
                params.put("content", "尊敬的用户，您好！\n" +
                        "您正在进行密码找回验证，请勿将验证码告知他人。\n" +
                        "验证码：" + code1 + "\n" +
                        ",有效期3分钟,请在页面输入上述验证码，完成密码重置操作。\n" +
                        "如非本人操作，请忽略此短信。\n" +
                        "如有任何问题，请拨打客服热线1388888888或发送邮件至TanNic1314@163.com。\n" +
                        "感谢您的使用！\n");
                return Result.succeed(sendMail(params), identifying1);
            default:
                throw new BusinessException("参数错误");
        }
    }
}